package com.gateway.config

import lombok.AllArgsConstructor
import lombok.extern.slf4j.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.gateway.config.GatewayProperties
import org.springframework.cloud.gateway.filter.GatewayFilter
import org.springframework.cloud.gateway.filter.GatewayFilterChain
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition
import org.springframework.cloud.gateway.route.Route
import org.springframework.cloud.gateway.route.RouteDefinition
import org.springframework.cloud.gateway.route.RouteLocator
import org.springframework.cloud.gateway.support.NameUtils
import org.springframework.context.annotation.Primary
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.stereotype.Component
import org.springframework.util.StringUtils
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import springfox.documentation.swagger.web.*
import java.util.*


@Slf4j
@AllArgsConstructor
@Component
@Primary
class SwaggerResourceConfig : SwaggerResourcesProvider {
    private val routeLocator: RouteLocator? = null
    private val gatewayProperties: GatewayProperties? = null
    override fun get(): List<SwaggerResource> {
        val resources: MutableList<SwaggerResource> = ArrayList()
        val routes: MutableList<String> = ArrayList()
        routeLocator!!.routes.subscribe { route: Route -> routes.add(route.id) }
        gatewayProperties!!.routes.stream().filter { routeDefinition: RouteDefinition -> routes.contains(routeDefinition.id) }.forEach { route: RouteDefinition ->
            route.predicates.stream()
                .filter { predicateDefinition: PredicateDefinition -> "Path".equals(predicateDefinition.name, ignoreCase = true) }
                .forEach { predicateDefinition: PredicateDefinition ->
                    resources.add(
                        swaggerResource(
                            route.id,
                            predicateDefinition.args[NameUtils.GENERATED_NAME_PREFIX + "0"]!!.replace("**", "v2/api-docs")
                        )
                    )
                }
        }
        return resources
    }

    private fun swaggerResource(name: String, location: String): SwaggerResource {
        val swaggerResource = SwaggerResource()
        swaggerResource.name = name
        swaggerResource.location = location
        swaggerResource.swaggerVersion = "2.0"
        return swaggerResource
    }
}

@RestController
@RequestMapping("/swagger-resources")
class SwaggerController @Autowired constructor(private val swaggerResources: SwaggerResourcesProvider) {
    @Autowired(required = false)
    private val securityConfiguration: SecurityConfiguration? = null

    @Autowired(required = false)
    private val uiConfiguration: UiConfiguration? = null

    @GetMapping("/configuration/security")
    fun securityConfiguration(): Mono<ResponseEntity<SecurityConfiguration>> = Mono.just(ResponseEntity<SecurityConfiguration>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK))

    @GetMapping("/configuration/ui")
    fun uiConfiguration(): Mono<ResponseEntity<UiConfiguration>> = Mono.just(ResponseEntity<UiConfiguration>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK))

    @GetMapping("")
    fun swaggerResources(): Mono<ResponseEntity<*>> = Mono.just(ResponseEntity(swaggerResources.get(), HttpStatus.OK))
}

@Component
class SwaggerHeaderFilter : AbstractGatewayFilterFactory<Any?>() {
    override fun apply(config: Any?): GatewayFilter {
        return GatewayFilter { exchange: ServerWebExchange, chain: GatewayFilterChain ->
            val request: ServerHttpRequest = exchange.request
            val path: String = request.uri.path
            if (!StringUtils.endsWithIgnoreCase(path, URI)) {
                return@GatewayFilter chain.filter(exchange)
            }
            val basePath = path.substring(0, path.lastIndexOf(URI))
            val newRequest: ServerHttpRequest = request.mutate().header(HEADER_NAME, basePath).build()
            val newExchange: ServerWebExchange = exchange.mutate().request(newRequest).build()
            return@GatewayFilter chain.filter(newExchange)
        }
    }

    companion object {
        private const val HEADER_NAME = "X-Forwarded-Prefix"
        private const val URI = "/v2/api-docs"
    }
}
